/*
 Copyright (c) 2013 MeherTJ G. All rights reserved.
 License: LGPL for personnal study or free software.
 */

/// @brief fraction
/// A/B

#pragma once

#include <iostream>
#if defined(__cpp_impl_three_way_comparison)
#include <compare>
#endif
#include "zen_number.h"

namespace Zen
{
	class Fraction
	{
	private:
		int mNumerator = 0;
		int mDenominator = 1;

	public:
		Fraction() = default;

		Fraction(Fraction const&) = default;

		Fraction& operator=(Fraction const&) = default;

		Fraction(int mNumerator)
		{
			Set(mNumerator, 1);
		}
		Fraction(int Numerator, int Denominator)
		{
			Set(Numerator, Denominator);
		}
		void Set(int Numerator, int Denominator = 1)
		{
			this->mNumerator = Numerator;
			this->mDenominator = Denominator;
			this->Reduce();
		}

		Fraction operator+(Fraction const& o) const
		{
			if (mDenominator == o.mDenominator)
			{
				return Fraction(mNumerator + o.mNumerator, mDenominator);
			} else
			{
				return Fraction(mNumerator * o.mDenominator + mDenominator * o.mNumerator, mDenominator * o.mDenominator);
			}
		}
		Fraction& operator+=(Fraction const& o)
		{
			if (mDenominator == o.mDenominator)
			{
				mNumerator += o.mNumerator;
			} else
			{
				mNumerator = mNumerator * o.mDenominator + mDenominator * o.mNumerator;
				mDenominator *= o.mDenominator;
				this->Reduce();
			}
			return *this;
		}

		Fraction operator-(Fraction const& o) const
		{
			if (mDenominator == o.mDenominator)
			{
				return Fraction(mNumerator - o.mNumerator, mDenominator);
			} else
			{
				return Fraction(mNumerator * o.mDenominator - mDenominator * o.mNumerator, mDenominator * o.mDenominator);
			}
		}
		Fraction& operator-=(Fraction const& o)
		{
			if (mDenominator == o.mDenominator)
			{
				mNumerator -= o.mNumerator;
			} else
			{
				mNumerator = mNumerator * o.mDenominator - mDenominator * o.mNumerator;
				mDenominator *= o.mDenominator;
				this->Reduce();
			}

			return *this;
		}
		// end minus

		Fraction operator*(Fraction const& o) const
		{
			return Fraction(mNumerator * o.mNumerator, mDenominator * o.mDenominator);
		}
		Fraction& operator*=(Fraction const& o)
		{
			mNumerator *= o.mNumerator;
			mDenominator *= o.mDenominator;
			this->Reduce();
			return *this;
		}

		Fraction operator/(Fraction const& o) const
		{
			return Fraction(mNumerator * o.mDenominator, mDenominator * o.mNumerator);
		}
		Fraction& operator/=(Fraction const& o)
		{
			mNumerator *= o.mDenominator;
			mDenominator *= o.mNumerator;
			this->Reduce();
			return *this;
		}

	public: // Get
		double GetValue() const
		{
			return (double)mNumerator / (double)mDenominator;
		}
		int Numerator() const
		{
			return mNumerator;
		}
		int Denominator() const
		{
			return mDenominator;
		}
		// compare with Fraction
#if defined(__cpp_impl_three_way_comparison)
		auto operator<=>(Fraction const& o) const
		{
			if (mDenominator == o.mDenominator) return mNumerator <=> o.mNumerator;
			return mNumerator * o.mDenominator <=> o.mNumerator * mDenominator;
		}
#endif
	private:
		Fraction& Reduce()
		{
			if (mNumerator && mDenominator)
			{
				bool neg = ((mNumerator ^ mDenominator) < 0);
				if (mNumerator < 0) mNumerator = -mNumerator;
				if (mDenominator < 0) mDenominator = -mDenominator;
				int gcd = Number::GetGCD(mNumerator, mDenominator);
				mNumerator /= (neg ? -gcd : gcd);
				mDenominator /= gcd;

			} else if (mDenominator == 0)
			{
				mNumerator = (mNumerator < 0 ? -1 : 1);
			}
			return *this;
		}
	};

	template <typename CharT>
	inline std::basic_ostream<CharT>& operator<<(std::basic_ostream<CharT>& os, Zen::Fraction const& frac)
	{
		os << frac.Numerator() << '/' << frac.Denominator();
		return os;
	}
} // namespace Zen
